package cn.com.bluemoon.daps.sync.core.sync;

import cn.com.bluemoon.daps.common.domain.UserInfoHolder;
import cn.com.bluemoon.daps.common.enums.DatabaseType;
import cn.com.bluemoon.daps.common.enums.SyncToEnv;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.common.toolkit.valueobject.PtSsh;
import cn.com.bluemoon.daps.model.entity.DapDataModel;
import cn.com.bluemoon.daps.model.entity.DapDataModelGroup;
import cn.com.bluemoon.daps.sync.config.PtServerConfig;
import cn.com.bluemoon.daps.sync.core.sync.sql.mysql.MysqlConnect;
import cn.com.bluemoon.daps.system.entity.DapSystemConfigSandbox;
import cn.com.bluemoon.daps.system.entity.DapSystemDatasource;
import cn.com.bluemoon.daps.system.entity.DapSystemDatasourceEnvironment;
import cn.com.bluemoon.daps.system.entity.DapSystemInfo;
import cn.hutool.db.Db;
import com.alibaba.druid.DbType;
import com.mysql.cj.jdbc.Driver;
import com.mysql.cj.jdbc.MysqlDataSource;
import lombok.*;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 对比全局变量
 *
 * @author Jarod.Kong
 */
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class GlobalConfig {
    /**
     * 任务id（发布同步主表dap_sync_prod_env_task.id）
     */
    private String logBizKey;

    /* 同步执行人 记录触发的同步信息 */
    protected UserInfoHolder.UserInfo userInfoExecutor;
    /**
     * 获取指定表的元数据&模型数据
     * 身份
     */
    private TableIdentity tableIdentity;
    /**
     * 是否开启sql检查
     */
    private boolean checkSqlLayer = false;
    /**
     * 数据库类型
     */
    private DbType dbType = DbType.mysql;
    /**
     * 模型信息
     */
    private DapDataModel model;
    /**
     * 系统信息
     */
    private DapSystemInfo system;
    /**
     * 数据源信息
     */
    private DataSourceAndEnv dataSourceAndEnv;
    /**
     * 默认组
     */
    private DapDataModelGroup defaultGroup;
    /**
     * 是否对结果集进行sink入库-标志（目前用于导入开发库中-对比完成后->控制是否结束还是接着入库
     */
    private boolean addSink;
    /**
     * 开发同步：针对模型没有而数据库有的表，目前需求是忽略数据库的表，而不进行删除表
     * 导入开发库表：针对模型有而数据库没有的表，目前需求是忽略数据库的表，而不进行删除表
     * 默认都不删除
     */
    private boolean canDropDbTable = false;
    /**
     * 针对字段名、表名、索引名是否不需要加反斜杠
     * 默认则为加反斜杠
     */
    private boolean noBlackQuote = false;
    /**
     * 同步到 同步到模型、开发、测试、预发布、生产
     */
    private SyncToEnv syncToEnv;
    /**
     * 同步环境中是否需要同步到沙箱中模拟
     */
    private boolean syncSandboxMock = false;
    /**
     * 获取本地模型中的版本为 true最新模型数据，false最新已发布版本数据
     */
    private boolean modelNewest = true;
    /**
     * ptosc执行-服务器
     */
    private PtServerConfig ptServerConfig;

    public Optional<TableIdentity> getTableIdentityOpt() {
        return Optional.ofNullable(tableIdentity);
    }

    public String getSyncExecBy() {
        if (userInfoExecutor == null) {
            return null;
        }
        return userInfoExecutor.getAccount();
    }

    /**
     * 增加直接获取数据源
     */
    public DapSystemDatasource getDataSource() {
        return dataSourceAndEnv.getDs();
    }

    /**
     * 增加直接获取数据源环境
     */
    public DapSystemDatasourceEnvironment getDataSourceEnv() {
        return dataSourceAndEnv.getEnv();
    }

    public Optional<DapSystemDatasourceEnvironment> getDataSourceEnvOpt() {
        return Optional.ofNullable(dataSourceAndEnv.getEnv());
    }

    /**
     * 提供直接获取数据源中的dbname
     */
    public Optional<String> getDsDbName() {
        if (getDataSourceEnvOpt().isPresent()) {
            return getDataSourceEnvOpt().map(d -> d.getDatabaseName());
        }
        // 尝试去allEnv中哪一个dbname
        List<DapSystemDatasourceEnvironment> allEnv = getDataSourceAndEnv().getAllEnv();
        if (allEnv != null) {
            return allEnv.stream().filter(d -> d.getDatabaseName() != null)
                    .map(DapSystemDatasourceEnvironment::getDatabaseName).findFirst();
        }
        return Optional.empty();
    }

    /**
     * 获取沙箱环境
     */
    public DapSystemConfigSandbox getDsSandboxEnv() {
        return dataSourceAndEnv.getSandboxEnv();
    }

    /**
     * 测试数据源使用，不进行
     */
    public Db testConnectDB() throws SQLException {
        return doCreateDb(true);
    }

    /**
     * 获取数据源
     */
    public Db findDb() throws SQLException {
        return doCreateDb(false);
    }

    /**
     * 获取目标同步环境
     */
    public Db findSyncEnvDb() throws SQLException {
        MysqlDataSource dataSource = getMysqlDataSource(true, getSyncEnvMysqlConnect());
        return findDb(dataSource);
    }

    /** 获取目标环境连接信息 */
    public MysqlConnect getSyncEnvMysqlConnect() {
        return getMysqlConnectByDs(false);
    }

    /** 获取沙箱环境连接信息 */
    public MysqlConnect getSandboxMysqlConnect() {
        return getMysqlConnectByDs(true);
    }

    /**
     * 根据同步环境进行初始化数据库名
     *
     * @return true 尝试创建数据库完成 false失败
     */
    public boolean initDbName(boolean syncSandboxMock) {
        MysqlConnect env = getMysqlConnectByDs(syncSandboxMock);
        String script = "create database if not exists " + env.getDatabase();
        return doDbScript(Collections.singletonList(script), env);
    }

    /**
     * 根据配置数据源获取mysql连接
     *
     * @param syncSandboxMock 根据入参进行判断是否取沙箱环境的链接信息
     * @return mysql连接
     */
    private MysqlConnect getMysqlConnectByDs(boolean syncSandboxMock) {
        DapSystemDatasourceEnvironment ds = getDataSourceEnv();
        if (syncSandboxMock) {
            MysqlConnect mysqlConnect = new MysqlConnect();
            DapSystemConfigSandbox sandbox = getDsSandboxEnv();
            mysqlConnect.setDbType(DatabaseType.MYSQL);
            String uniqueDbName = SandboxUtils.uniqueDbName(ds.getHost(), ds.getPort(), ds.getDatabaseName());
            mysqlConnect.setDatabase(uniqueDbName);
            mysqlConnect.setHost(sandbox.getHost());
            mysqlConnect.setPort(sandbox.getPort());
            mysqlConnect.setPassword(sandbox.decryptPassword());
            mysqlConnect.setUser(sandbox.getUsername());
            log.warn("当前配置为同步到沙箱环境，连接配置为{}", mysqlConnect.toString(true));
            return mysqlConnect;
        } else {
            return new MysqlConnect(ds);
        }
    }

    private Db doCreateDb(boolean notTestConnect) throws SQLException {
        Db db = null;
        try {
            boolean syncSandboxMock = isSyncSandboxMock();
            log.info("获取【{}】数据源信息，不进行测试连接数据源", syncSandboxMock ? "沙箱" : "目标环境");
            MysqlConnect env = getMysqlConnectByDs(syncSandboxMock);
            MysqlDataSource dataSource = getMysqlDataSource(notTestConnect, env);
            db = Db.use(dataSource);
        } catch (Exception e) {
            throw new SQLException(e);
        }
        return db;
    }

    /** 没有指定数据库名的情况下进行操作 */
    private boolean doDbScript(List<String> scripts, MysqlConnect env) {
        // 尝试忽略数据库名进行连接，然后查看是否存在改库名，没有则创建库名
        String url = String.format("jdbc:%s://%s:%d", env.getDbTypeName(), env.getHost(), env.getPort());
        try {
            Class.forName(Driver.class.getCanonicalName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        for (String script : scripts) {
            try (Connection conn = DriverManager.getConnection(url, env.getUser(), env.getPassword());
                 PreparedStatement ps = conn.prepareStatement(script)) {
                boolean status = ps.execute();
                log.info("db - script:{}-status:{}", script, status);
            } catch (SQLException e) {
                log.error("尝试执行{}失败", script, e);
                return false;
            }
        }
        return true;
    }

    /** 获取沙箱环境 */
    public Db findSandboxDb() throws SQLException {
        MysqlDataSource dataSource = getMysqlDataSource(true, getSandboxMysqlConnect());
        return findDb(dataSource);
    }

    /**
     * 基于全局变量构建数据源
     *
     * @return 1
     */
    private MysqlDataSource getMysqlDataSource(boolean testConnect, MysqlConnect env) throws SQLException {
        MysqlDataSource ds = null;
        try {
//          jdbc:mysql://192.168.48.122:3306/data_asset_platform
            String url = String.format("jdbc:%s://%s:%d/%s?createDatabaseIfNotExist=true", env.getDbTypeName(), env.getHost(),
                    env.getPort(), env.getDatabase()),
                    username = env.getUser(),
                    password = env.getPassword();
            log.info("1、获取到模型{}的数据源信息{}", getModel().getName(), url);
            ds = new MysqlDataSource();
            ds.setAutoSlowLog(true);
            ds.setServerName(env.getHost());
            ds.setPort(env.getPort());
            ds.setDatabaseName(env.getDatabase());
            ds.setAutoReconnect(!testConnect);
            ds.setElideSetAutoCommits(true);
            ds.setUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
        } catch (Exception e) {
            log.error("创建连接信息失败", e);
            throw new SQLException(e);
        }
        return ds;
    }

    /**
     * 根据ptServerConfig获取ptssh信息
     */
    public PtSsh getPtSsh() {
        PtSsh ptSsh = ptServerConfig.getPtSsh();
        BmAssetUtils.notNull(ptSsh, "获取pt服务器信息失败，无法执行ptosc命令！");
        return ptSsh;
    }

    /**
     * 创建db
     */
    public Db findDb(DataSource dataSource) {
        return Db.use(dataSource);
    }

    /**
     * 重建数据库
     *
     * @param sandboxSync 沙箱
     * @return res
     */
    public boolean rebuildDbName(boolean sandboxSync) {
        MysqlConnect env = getMysqlConnectByDs(sandboxSync);
        return doDbScript(Arrays.asList("drop database if exists " + env.getDatabase(),
                "create database if not exists " + env.getDatabase()), env);
    }

    /** 返回沙箱还是目标环境信息 */
    public MysqlConnect getMysqlConnectByGlobalConfig() {
        return getMysqlConnectByDs(isSyncSandboxMock());
    }

    @RequiredArgsConstructor
    @Getter
    public static class TableIdentity {
        private final List<String> tableIds;
        private final List<String> tableNames;

        public String[] getTableNamesArr() {
            if (tableNames == null) {
                return null;
            }
            return tableNames.toArray(new String[0]);
        }

        public List<String> getTableNamesDistinct() {
            return Optional.ofNullable(tableNames)
                    .map(t -> t.stream().distinct().collect(Collectors.toList()))
                    .orElse(tableNames);
        }

        public List<String> getTableIdsDistinct() {
            return Optional.ofNullable(tableIds)
                    .map(t -> t.stream().distinct().collect(Collectors.toList()))
                    .orElse(tableIds);
        }
    }

    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @Getter
    public static class DataSourceAndEnv {
        /**
         * 数据源信息
         */
        private DapSystemDatasource ds;
        /**
         * 目标同步环境信息
         */
        private DapSystemDatasourceEnvironment env;
        /**
         * 增加同步功能中依赖的沙箱环境 (目前是全同步功能都用同一个）
         */
        private DapSystemConfigSandbox sandboxEnv;

        private List<DapSystemDatasourceEnvironment> allEnv;
    }
}
